package com.small.sentinel.slots.block.degrade.circuitbreaker;

import com.small.sentinel.Context;
import com.small.sentinel.Entry;
import com.small.sentinel.slots.block.degrade.DegradeRule;
import com.small.sentinel.slots.statistic.base.LeapArray;
import com.small.sentinel.slots.statistic.base.WindowWrap;

import java.util.List;
import java.util.concurrent.atomic.LongAdder;

import static com.small.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO;

/**
 * 异常断路器
 */
public class ExceptionCircuitBreaker extends AbstractCircuitBreaker {

    // 熔断策略 1-异常比例 2-异常数
    private final int strategy;
    // 最小请求数
    private final int minRequestAmount;
    // 阈值（异常比例下：比例阀值  异常数：异常数）
    private final double threshold;
    //统计窗口
    private final LeapArray<SimpleErrorCounter> stat;

    ExceptionCircuitBreaker(DegradeRule rule) {
        this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs()));
    }

    ExceptionCircuitBreaker(DegradeRule rule, LeapArray<SimpleErrorCounter> stat) {
        super(rule);
        this.strategy = rule.getGrade();
        this.minRequestAmount = rule.getMinRequestAmount();
        this.threshold = rule.getCount();
        this.stat = stat;
    }

    @Override
    public void onRequestComplete(Context context) {

        Entry curEntry = context.getCurEntry();
        WindowWrap<SimpleErrorCounter> currentWindow = stat.currentWindow();
        SimpleErrorCounter counter = currentWindow.value();

        //添加计数
        counter.totalCount.increment();
        Throwable error = curEntry.getError();
        if (error != null) {
            counter.totalCount.increment();
        }

        handleStateChangeWhenThresholdExceeded(error);
    }

    @Override
    void resetStat() {
        stat.currentWindow().value().reset();
    }

    /**
     * 超过阈值时处理状态更改
     *
     * @param error
     */
    private void handleStateChangeWhenThresholdExceeded(Throwable error) {
        // 1. 如果当前状态是OPEN，不变，由父类tryPass方法负责从OPEN变为HALF_OPEN
        if (currentState.get() == State.OPEN) {
            return;
        }

        // 2. 如果当前状态是HALF_OPEN，判断本次请求是否发生异常，如果发生异常，重新变为OPEN，否则变为CLOSE
        if (currentState.get() == State.HALF_OPEN) {
            if (error == null) {
                fromOpenToHalfOpen();
            } else {
                transformToOpen();
            }
            return;
        }

        // 3. 当前是CLOSE状态，根据统计数据，判断是否需要OPEN

        List<SimpleErrorCounter> counters = stat.values();
        long errCount = 0;
        long totalCount = 0;
        for (SimpleErrorCounter counter : counters) {
            errCount += counter.errorCount.sum();
            totalCount += counter.totalCount.sum();
        }
        if (totalCount < minRequestAmount) {
            return;
        }
        double curCount = errCount;
        if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) {
            // 使用异常比率
            curCount = errCount * 1.0d / totalCount;
        }
        if (curCount > threshold) {
            transformToOpen();
        }
    }

    static class SimpleErrorCounter {

        private LongAdder errorCount;
        private LongAdder totalCount;

        public SimpleErrorCounter() {
            this.errorCount = new LongAdder();
            this.totalCount = new LongAdder();
        }

        public LongAdder getErrorCount() {
            return errorCount;
        }

        public LongAdder getTotalCount() {
            return totalCount;
        }

        public SimpleErrorCounter reset() {
            errorCount.reset();
            totalCount.reset();
            return this;
        }
    }

    static class SimpleErrorCounterLeapArray extends LeapArray<SimpleErrorCounter> {

        public SimpleErrorCounterLeapArray(int sampleCount, int intervalInMs) {
            super(sampleCount, intervalInMs);
        }

        @Override
        protected WindowWrap<SimpleErrorCounter> resetWindowTo(WindowWrap<SimpleErrorCounter> w, long startTime) {
            w.resetTo(startTime);
            w.value().reset();
            return w;
        }

        @Override
        public SimpleErrorCounter newEmptyBucket(long timeMillis) {
            return new SimpleErrorCounter();
        }

    }

}
